// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/base/synchronization/one_writer_seqlock.h"

#include <stdlib.h>

#include "base/atomic_ref_count.h"
#include "base/macros.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {

// Basic test to make sure that basic operation works correctly.

struct TestData {
    unsigned a, b, c;
};

class BasicSeqLockTestThread : public base::PlatformThread::Delegate {
public:
    BasicSeqLockTestThread() { }

    void Init(OneWriterSeqLock* seqlock,
        TestData* data,
        base::AtomicRefCount* ready)
    {
        seqlock_ = seqlock;
        data_ = data;
        ready_ = ready;
    }
    void ThreadMain() override
    {
        while (base::AtomicRefCountIsZero(ready_)) {
            base::PlatformThread::YieldCurrentThread();
        }

        for (unsigned i = 0; i < 1000; ++i) {
            TestData copy;
            base::subtle::Atomic32 version;
            do {
                version = seqlock_->ReadBegin();
                copy = *data_;
            } while (seqlock_->ReadRetry(version));

            EXPECT_EQ(copy.a + 100, copy.b);
            EXPECT_EQ(copy.c, copy.b + copy.a);
        }

        base::AtomicRefCountDec(ready_);
    }

private:
    OneWriterSeqLock* seqlock_;
    TestData* data_;
    base::AtomicRefCount* ready_;

    DISALLOW_COPY_AND_ASSIGN(BasicSeqLockTestThread);
};

#if defined(OS_ANDROID)
#define MAYBE_ManyThreads FLAKY_ManyThreads
#else
#define MAYBE_ManyThreads ManyThreads
#endif
TEST(OneWriterSeqLockTest, MAYBE_ManyThreads)
{
    OneWriterSeqLock seqlock;
    TestData data = { 0, 0, 0 };
    base::AtomicRefCount ready = 0;

    ANNOTATE_BENIGN_RACE_SIZED(&data, sizeof(data), "Racey reads are discarded");

    static const unsigned kNumReaderThreads = 10;
    BasicSeqLockTestThread threads[kNumReaderThreads];
    base::PlatformThreadHandle handles[kNumReaderThreads];

    for (unsigned i = 0; i < kNumReaderThreads; ++i)
        threads[i].Init(&seqlock, &data, &ready);
    for (unsigned i = 0; i < kNumReaderThreads; ++i)
        ASSERT_TRUE(base::PlatformThread::Create(0, &threads[i], &handles[i]));

    // The main thread is the writer, and the spawned are readers.
    unsigned counter = 0;
    for (;;) {
        seqlock.WriteBegin();
        data.a = counter++;
        data.b = data.a + 100;
        data.c = data.b + data.a;
        seqlock.WriteEnd();

        if (counter == 1)
            base::AtomicRefCountIncN(&ready, kNumReaderThreads);

        if (base::AtomicRefCountIsZero(&ready))
            break;
    }

    for (unsigned i = 0; i < kNumReaderThreads; ++i)
        base::PlatformThread::Join(handles[i]);
}

} // namespace device
